/*
 * Copyright (c) 2009, The MilkyTracker Team.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  LoaderPSM.cpp
 *  MilkyPlay Module Loader: Version 1/2 of Epic MegaGames MASI
 */
#include "Loaders.h"
#include <stdio.h>

static bool PATTTest(mp_ubyte* p, mp_sint32& size)
{
    if (p[0] != 'P') return false;

    // type Pxxx
    if (p[1] != 'A')
    {
        if (p[1] < '0' || p[1] > '9') return false;
        if ((p[2] < '0' || p[2] > '9') && p[2] != ' ') return false;
        if ((p[3] < '0' || p[3] > '9') && p[3] != ' ') return false;
        size = 4;
        return true;
    }
    // type PATTxxxx
    else
    {
        if (p[2] != 'T' || p[3] != 'T')
            return false;

        if (p[4] < '0' || p[4] > '9') return false;
        if ((p[5] < '0' || p[5] > '9') && p[5] != ' ') return false;
        if ((p[6] < '0' || p[6] > '9') && p[6] != ' ') return false;
        if ((p[7] < '0' || p[7] > '9') && p[7] != ' ') return false;
        size = 8;
        return true;
    }
}

#define RELEASE_PATTERNS { \
        for (mp_sint32 i = 0; i < 1024; i ++) \
            if (patterns[i]) \
            { \
                delete[] patterns[i]; \
                patterns[i] = NULL; \
            } \
        delete[] patterns; \
}

const char* LoaderPSMv2::identifyModule(const mp_ubyte* buffer)
{
    // check for .PSM (new) module
    if (!memcmp(buffer, "PSM\x20", 4))
    {
        return "PSMv2";
    }

    // this is not an .PSM
    return NULL;
}

mp_sint32 LoaderPSMv2::load(XMFileBase& f, XModule* module)
{
    module->cleanUp();

    // this will make code much easier to read
    TXMHeader* header = &module->header;
    TXMInstrument* instr = module->instr;
    TXMSample* smp = module->smp;
    TXMPattern* phead = module->phead;

    // we're already out of memory here
    if (!phead || !instr || !smp)
        return -7;

    // size for 1024 patterns
    // should be enough, really
    mp_ubyte** patterns = new mp_ubyte *[1024];
    if (patterns == NULL)
        return -7;

    mp_uint32* patternSizes = new mp_uint32[1024];
    if (patternSizes == NULL)
    {
        delete[] patterns;
        return -7;
    }

    for (mp_sint32 j = 0; j < 1024; j++)
        patterns[j] = NULL;

    memset(patternSizes, 0, 1024 * sizeof(mp_uint32));

    //mp_uint32 songSize = 0;
    mp_uint32 patIndex = 0;
    mp_uint32 insIndex = 0;
    mp_uint32 smpIndex = 0;

    mp_ubyte* songSignature = NULL;
    mp_uint32 signatureSize = 0;

    bool sinaria = false;

    while (true)
    {

        mp_ubyte ID[4];

        mp_uint32 bytesRead = f.read(&ID, 4, 1);

        if (bytesRead != 4)
            break;

        switch (LittleEndian::GET_DWORD(ID))
        {
            // 'PSM '
            case 0x204d5350:

                memcpy(header->sig, "PSM", 3);

                f.readDword();  // consume chunk size (song size)
                break;

            // 'FILE'
            case 0x454c4946:    // just eat ID
                break;

            // 'SDFT'
            case 0x54464453:    // whatever this is
                signatureSize = f.readDword();  // get chunk size

                delete[] songSignature;
                songSignature = new mp_ubyte[signatureSize];
                // typically size is 8 and buffer will contain "MAINSONG"
                f.read(songSignature, 1, signatureSize);
                break;

            // 'TITL'
            case 0x4c544954:    // title chunk... song contains title
            {
                mp_uint32 size = f.readDword(); // get chunk size

                mp_ubyte* buffer = new mp_ubyte[size];

                f.read(buffer, 1, size);

                // just make sure title fits into our name field
                if (size > sizeof(header->name))
                    size = sizeof(header->name);

                memcpy(header->name, buffer, size);

                delete[] buffer;

                break;
            }

            // 'PBOD'
            case 0x444f4250:    // pattern chunk
            {
                mp_uint32 size = f.readDword(); // get chunk size

                patterns[patIndex] = new mp_ubyte[size];

                patternSizes[patIndex] = size;

                f.read(patterns[patIndex++], 1, size);

                break;
            }

            // 'SONG'
            case 0x474E4F53:    // song chunk
            {
                f.readDword();  // consume chunk size

                mp_ubyte buffer[9];
                f.read(buffer, 1, 8); buffer[8] = '\0';
                /*if (memcmp(buffer,songSignature,signatureSize)!=0)
                   {
                    printf("Something went wrong here!");
                    exit(0);
                   }
                   delete buffer;*/

                #ifdef VERBOSE
                printf("Song (pos: %i): %s\n", header->ordnum, buffer);
                #endif

                f.readByte();   // what's this?
                f.readByte();   // what's this?

                mp_ubyte numchannels = f.readByte();

                if (numchannels > header->channum)
                    header->channum = numchannels;

                break;
            }

            // 'OPLH'
            case 0x484c504f:
            {
                mp_uint32 size = f.readDword();

                // the following is a little bit messy, but i dunno all the fields
                // so the data i'll need is looked up by a brute force search
                mp_ubyte* buffer = new mp_ubyte[size];

                f.read(buffer, 1, size);

                bool helperPattern = false;

                mp_uint32 i = 0;
                for (;;)
                {
                    if (buffer[i] == 0x07 && buffer[i + 2] == 0x08)
                    {
                        i++;
                        if (header->ordnum == 0)
                        {
                            header->tempo = buffer[i];  // tickspeed
                            header->speed = buffer[i + 2]; // BPM
                        }
                        // if there are more then 1 songs contained in the file we need to create a little
                        // helper pattern to set speed and tempo for that particular song
                        else
                        {
                            char patSig[10];
                            sprintf(patSig, "P%i  ", patIndex % 999);

                            patterns[patIndex] = new mp_ubyte[20];
                            memset(patterns[patIndex], 0, 20);
                            memcpy(patterns[patIndex] + 4, patSig, 4);
                            patternSizes[patIndex] = 20;

                            mp_ubyte* pattern = patterns[patIndex];

                            pattern[0] = 0xFF;
                            pattern[1] = 0xFF;
                            pattern[2] = 0xFF;
                            pattern[3] = 0xFF;

                            pattern[8] = 0x01;  // just one row
                            pattern[10] = 0x0A; // 10 bytes in size
                            pattern[12] = 0x10; // read effect + operand
                            pattern[13] = 0x00; // channel 0 and
                            pattern[14] = 0x3E; // set BPM
                            pattern[15] = buffer[i + 2];
                            pattern[16] = 0x10; // read effect + operand
                            pattern[17] = 0x01; // channel 1
                            pattern[18] = 0x3D; // set speed
                            pattern[19] = buffer[i];

                            helperPattern = true;

                            patIndex++;
                        }
                        break;
                    }
                    i++;

                    if (i >= size)
                        break;

                }

                mp_sint32 patIDSize = 0;
                while (!PATTTest(buffer + i + 1, patIDSize) && i < size)
                    i++;

                if (patIDSize == 8)
                    sinaria = true;

                // hier lief was schief
                if (i >= size || (patIDSize != 4 && patIDSize != 8))
                {
                    delete[] buffer;
                    RELEASE_PATTERNS;
                    return -8;
                }

                //header->ordnum = 0;
                // place helper pattern right before the song
                if (header->ordnum != 0 && helperPattern)
                    header->ord[header->ordnum++] = patIndex - 1;

                while (buffer[i + 1] == 'P' && i < (size - 5))
                {
                    for (mp_uint32 j = 0; j < patIndex; j++)
                    {
                        if (memcmp(buffer + i + 1, patterns[j] + 4, patIDSize) == 0)
                        {
                            header->ord[header->ordnum] = j;
                            break;
                        }
                    }
                    i += patIDSize + 1;
                    header->ordnum++;
                }

                delete[] buffer;

                break;
            }

            // 'DATE'
            case 0x45544144:    // date chunk (ASCII string)
            // 'PPAN'
            case 0x4E415050:    // panning found in sinaria PSM modules
            // 'PATT'
            case 0x54544150:    // list of used patterns? pretty useless to me...
            // 'DSAM'
            case 0x4d415344:    // another useless chunk...
            {
                mp_uint32 size = f.readDword();
                // read and discard
                mp_ubyte* buffer = new mp_ubyte[size];
                f.read(buffer, 1, size);
                delete[] buffer;
                break;
            }

            // 'DSMP'
            case 0x504d5344:
            {
                mp_uint32 size = f.readDword();

                mp_uint32 infoSize = size > 96 ? 96 : size;

                // read sample info first
                mp_ubyte* buffer = new mp_ubyte[infoSize];
                f.read(buffer, 1, infoSize);

                if (infoSize == 96)
                {
                    mp_sint32 offsetName = 13;
                    mp_sint32 offsetSamplen = 54;
                    mp_sint32 offsetLoopstart = 58;
                    mp_sint32 offsetLoopEnd = 62;
                    mp_sint32 offsetVolume = 68;
                    mp_sint32 offsetC4spd = 73;

                    if (sinaria)
                    {
                        offsetName += 4;
                        offsetSamplen += 4;
                        offsetLoopstart += 4;
                        offsetLoopEnd += 4;
                        offsetVolume += 5;
                        offsetC4spd += 5;
                    }

                    memcpy(instr[insIndex].name, buffer + offsetName, 24);

                    if (size > 96)
                    {

                        instr[insIndex].samp = 1;               // one sample used
                        for (mp_sint32 j = 0; j < 120; j++)
                            instr[insIndex].snum[j] = smpIndex; // build sample table

                        smp[smpIndex].samplen = (mp_sint32) LittleEndian::GET_DWORD(buffer + offsetSamplen);       // sample size
                        smp[smpIndex].loopstart = (mp_sint32) LittleEndian::GET_DWORD(buffer + offsetLoopstart);       // loop start
                        smp[smpIndex].looplen = ((mp_sint32) LittleEndian::GET_DWORD(buffer + offsetLoopEnd)) -
                                                ((mp_sint32) LittleEndian::GET_DWORD(buffer + offsetLoopstart)) + 1; // loop length

                        mp_sint32 finetune = 0;
                        if (sinaria)
                            finetune = buffer[offsetVolume - 1] & 0xf;

                        smp[smpIndex].vol = module->vol127to255(buffer[offsetVolume]);      // volume

                        smp[smpIndex].flags = 1;                        // set volume flag

                        if (buffer[0] & 0x80)
                        {
                            smp[smpIndex].type = 1;                 // looped sample
                                                                    // correct looplen if necessary
                            if ((smp[smpIndex].loopstart + smp[smpIndex].looplen) > smp[smpIndex].samplen)
                                smp[smpIndex].looplen -= (smp[smpIndex].loopstart + smp[smpIndex].looplen) - smp[smpIndex].samplen;
                        }

                        mp_uint32 c4speed = (mp_uword) LittleEndian::GET_WORD(buffer + offsetC4spd);
                        mp_uint32 ftC4Speed = module->sfinetunes[finetune];

                        module->convertc4spd(c4speed * ftC4Speed / 8448,
                                             &smp[smpIndex].finetune, &smp[smpIndex].relnote);

                        //module->convertc4spd((mp_uword)LittleEndian::GET_WORD(buffer+offsetC4spd),&smp[smpIndex].finetune,&smp[smpIndex].relnote);

                        ASSERT(smp[smpIndex].samplen + 96 == size);

                        if (module->loadModuleSample(f, smpIndex, XModule::ST_DELTA) != 0)
                        {
                            delete[] buffer;
                            RELEASE_PATTERNS;
                            return -7;
                        }

                        //if (sinaria)
                        //{
                        //	if (smp[smpIndex].samplen > 2)
                        //		smp[smpIndex].samplen-=2;
                        //	if (smp[smpIndex].looplen > 2)
                        //		smp[smpIndex].looplen-=2;
                        //}

                        smpIndex++;

                    }

                    insIndex++;

                }

                delete[] buffer;

                break;
            }

            default:
                #ifdef VERBOSE
                printf("Unknown chunk: %X\n", ID);
                #endif
                return -8;
                break;

        }

    }

    if (songSignature)
        delete[] songSignature;

    mp_ubyte* pattern = new mp_ubyte[32 * 256 * 5];

    if (pattern == NULL)
    {
        RELEASE_PATTERNS;
        return -7;
    }

    for (mp_uint32 i = 0; i < patIndex; i++)
    {

        for (mp_sint32 j = 0; j < 32 * 256; j++)
        {
            pattern[j * 5] = 0xFF;
            pattern[j * 5 + 1] = 0xFF;
            pattern[j * 5 + 2] = 0xFF;
            pattern[j * 5 + 3] = 0;
            pattern[j * 5 + 4] = 0;
        }

        mp_sint32 patIDSize = 0;
        if (!PATTTest(patterns[i] + 4, patIDSize))
        {
            RELEASE_PATTERNS;
            return -7;
        }

        mp_sint32 offset = 6 + patIDSize;
        mp_ubyte* packed = patterns[i] + offset;

        mp_uint32 index = 0;

        mp_uint32 row = 0;

        mp_uint32 maxChannels = 0;

        while (index < (patternSizes[i] - offset))
        {

            mp_uint32 size = ((mp_uword) LittleEndian::GET_WORD(packed + index)) - 2;
            index += 2;   // advance pointer

            mp_uint32 dstIndex = index + size;

            if (size)
            {

                do
                {

                    mp_ubyte pi = packed[index++];

                    mp_uint32 chn = packed[index++];

                    if (chn > maxChannels)
                        maxChannels = chn;

                    mp_ubyte* slot = pattern + (row * 32 * 5) + chn * 5;

                    // note
                    if (pi & 0x80)
                    {
                        mp_ubyte note = packed[index++];

                        // key off note
                        if (note == 255)
                            note = 254;
                        else if (patIDSize == 8)
                        {
                            note = ((note + 23) / 12) * 16 + (((note + 23)) % 12);
                        }

                        slot[0] = note;
                    }
                    // instrument
                    if (pi & 0x40)
                    {
                        slot[1] = packed[index++];
                    }
                    // volume
                    if (pi & 0x20)
                    {
                        slot[2] = packed[index++];
                    }
                    // effect & operand
                    if (pi & 0x10)
                    {
                        slot[3] = packed[index++];
                        slot[4] = packed[index++];

                        if (slot[3] == 0x29)
                        {
                            slot[4] = packed[index += 2];
                        }
                        else if (slot[3] == 0x2D)
                        {
                            slot[4] = packed[index += 3];
                        }

                    }

                }
                while (index < dstIndex);

                #ifdef VERBOSE
                if (index != dstIndex)
                    printf("nagnag ");
                #endif

            }

            row++;
        }

        maxChannels++;

        if (maxChannels > header->channum)
            maxChannels = header->channum;

        // convert pattern here:
        phead[i].rows = (mp_uword) LittleEndian::GET_WORD(patterns[i] + offset - 2);
        phead[i].effnum = 3;
        phead[i].channum = maxChannels;

        phead[i].patternData = new mp_ubyte[phead[i].rows * maxChannels * 8];

        if (phead[i].patternData == NULL)
        {
            delete[] pattern;
            RELEASE_PATTERNS;
            return -7;
        }

        memset(phead[i].patternData, 0, maxChannels * 8 * phead[i].rows);

        mp_ubyte* dstSlot = phead[i].patternData;
        for (row = 0; row < phead[i].rows; row++)
        {
            for (mp_uint32 c = 0; c < maxChannels; c++)
            {
                const mp_ubyte* srcSlot = pattern + row * 32 * 5 + c * 5;

                mp_ubyte note = srcSlot[0];

                mp_ubyte finalNote = 0;

                if (note < 254)
                {
                    finalNote = ((note >> 4) * 12 + (note & 0x0f)) + 1;

                    if (finalNote > 120)
                    {
                        #ifdef VERBOSE
                        printf("Wrong note: %i", finalNote);
                        #endif
                        finalNote = 0;
                    }
                }
                else if (note == 254)
                {
                    finalNote = 122; // key off, s3m style
                }

                dstSlot[0] = finalNote;

                if (srcSlot[1] != 0xFF)
                    dstSlot[1] = srcSlot[1] + 1;

                if (srcSlot[2] <= 128)
                {

                    dstSlot[2] = 0xC;
                    dstSlot[3] = XModule::vol127to255(srcSlot[2]);

                }

                mp_ubyte eff = srcSlot[3];
                mp_ubyte op = srcSlot[4];

                switch (eff)
                {
                    // set sample offset
                    case 0x29:
                        eff = 0x09;
                        break;

                    // set BPM
                    case 0x3D:
                        eff = 0x1C;
                        break;

                    // set tickspeed
                    case 0x3E:
                        eff = 0x16;
                        break;

                    // pattern break
                    case 0x34:
                        eff = 0x0D;
                        break;

                    // vibrato
                    case 0x15:
                        eff = 0x04;
                        break;

                    // tone porta
                    case 0x0F:
                        eff = 0x03;
                        break;

                    // arpeggio
                    case 0x47:
                        eff = 0x20;
                        break;

                    // portamento up
                    case 0x0C:
                        eff = 0x4D;
                        break;

                    // portamento down
                    case 0x0E:
                        eff = 0x4E;
                        break;

                    // volume slide up = MDL volslide up
                    case 02:
                        eff = 0x45;
                        op <<= 1;
                        break;

                    // volume slide down = MDL volslide down
                    case 04:
                        eff = 0x46;
                        op <<= 1;
                        break;

                    // extra fine volume slide up
                    case 01:
                        eff = 0x4B;
                        op <<= 1;
                        break;

                    // extra fine volume slide down
                    case 03:
                        eff = 0x4C;
                        op <<= 1;
                        break;

                    // retrig note
                    case 0x2A:
                        eff = 0x1B;
                        break;

                    // fine portamento up
                    case 11:
                        eff = 0x31;
                        break;

                    // fine portamento down
                    case 13:
                        eff = 0x32;
                        break;

                    // note-cut
                    case 0x2B:
                        eff = 0x3C;
                        break;

                    // note-delay
                    case 0x2C:
                        eff = 0x3D;
                        break;

                    // jump loop
                    case 0x35:
                        eff = 0x36;
                        break;

                    // pattern delay
                    case 0x36:
                        eff = 0x3E;
                        break;

                    // position jump
                    case 50:
                        eff = 0x0B;
                        break;

                    /*// tone porta + volslide
                       case 17:
                        eff = 0x05;
                        if (op) op >>= 1;
                            break;*/

                    // tone porta + volslide up
                    case 16:
                        dstSlot[6] = 0x45;
                        dstSlot[7] = op << 1;

                        eff = 0x03;
                        op = 0;
                        break;

                    // tone porta + volslide down
                    case 17:
                        dstSlot[6] = 0x46;
                        dstSlot[7] = op << 1;

                        eff = 0x03;
                        op = 0;
                        break;

                    // vibrato + volslide up
                    case 0x17:
                        dstSlot[6] = 0x45;
                        dstSlot[7] = op << 1;

                        eff = 0x04;
                        op = 0;
                        break;

                    // vibrato + volslide down
                    case 0x18:
                        dstSlot[6] = 0x46;
                        dstSlot[7] = op << 1;

                        eff = 0x04;
                        op = 0;
                        break;

                    // tremolo
                    case 0x1F:
                        eff = 0x07;
                        break;

                    // set panning
                    case 0x49:
                        eff = 0x08;
                        break;

                    default:
                        #ifdef VERBOSE
                        if (eff != 0)
                        {
                            printf("%i, %x, %x\n", i, eff, op);
                        }
                        #endif
                        //eff = op = 0;
                        break;
                }

                //eff = op = 0;

                /*if (i == 13 && c == 0)
                   {
                    printf("%x, %x, %x, %x\n",finalNote,dstSlot[1],eff,op);
                   }*/

                dstSlot[4] = eff;
                dstSlot[5] = op;

                dstSlot += 8;
            }

        }

        bool dummyPattern = (*(patterns[i]) == 0xFF
                             && *(patterns[i] + 1) == 0xFF
                             && *(patterns[i] + 2) == 0xFF
                             && *(patterns[i] + 3) == 0xFF);

        // mark song start with effect 0x1E + operand 0xFF
        if (dummyPattern && phead[i].rows == 1)
        {
            phead[i].patternData[2] = XModule::SubSongMarkEffect;
            phead[i].patternData[3] = XModule::SubSongMarkOperand;
        }

    }

    delete[] pattern;

    RELEASE_PATTERNS;

    header->smpnum = smpIndex;
    header->insnum = insIndex;
    header->patnum = patIndex;

    header->mainvol = 255;

    module->setDefaultPanning();

    module->postProcessSamples();

    strcpy(header->tracker, "Epic Megagames MASI <new>");

    return 0;
}

//////////////////////////////////////////////////////////////////////////
// Old epic pinball PSM loader. Not much tested yet					    //
// Warning: This is an one-by-one conversion of an assembler version ;) //
//////////////////////////////////////////////////////////////////////////
#define MEMREAD(dst, size, count) \
    memcpy(dst, buffer + memPos, size* count); \
    memPos += size* count

#define MEMSEEK(position) \
    memPos = position;

#define MEMADVANCE(amount) \
    memPos += amount;

const char* LoaderPSMv1::identifyModule(const mp_ubyte* buffer)
{
    // check for .PSM (old) module
    if (!memcmp(buffer, "PSM\xFE", 4))
    {
        return "PSMv1";
    }

    // this is not an .PSM
    return NULL;
}

mp_sint32 LoaderPSMv1::load(XMFileBase& f, XModule* module)
{
    mp_uint32 memPos = 0;

    module->cleanUp();

    // this will make code much easier to read
    TXMHeader* header = &module->header;
    TXMInstrument* instr = module->instr;
    TXMSample* smp = module->smp;
    TXMPattern* phead = module->phead;

    // we're already out of memory here
    if (!phead || !instr || !smp)
        return -7;

    mp_ubyte* buffer = new mp_ubyte[f.sizeWithBaseOffset()];

    if (buffer == NULL)
        return -7;

    f.read(buffer, 1, f.sizeWithBaseOffset());

    MEMSEEK(0);

    MEMREAD(header->sig, 1, 3); // PSM signature

    MEMADVANCE(1);

    MEMREAD(header->name, 1, 20); // Don't know how long this is actually

    header->tempo = buffer[67]; // Tickspeed

    header->speed = buffer[68];  // BPM

    header->mainvol = 255;

    header->ordnum = buffer[72];

    header->insnum = header->smpnum = buffer[76];

    header->patnum = buffer[74];

    MEMSEEK((mp_sint32) LittleEndian::GET_DWORD(buffer + 82));

    MEMREAD(header->ord, 1, header->ordnum);

    MEMSEEK((mp_sint32) LittleEndian::GET_DWORD(buffer + 94));

    mp_uint32 samplePtrs[256];

    mp_uint32 maxInstruments = 0;

    mp_sint32 i;

    for (i = 0; i < header->insnum; i++)
    {

        mp_ubyte ins[64];
        MEMREAD(ins, 1, 64);

        // sample size
        if ((mp_sint32) LittleEndian::GET_DWORD(ins + 48) > 0)
        {
            mp_uint32 insIndex = ins[45] - 1;

            if (insIndex > maxInstruments)
                maxInstruments = insIndex;

            memcpy(smp[i].name, ins, 12);

            memcpy(instr[insIndex].name, ins + 13, 22);

            samplePtrs[i] = (mp_sint32) LittleEndian::GET_DWORD(ins + 37); // store sample offset

            instr[insIndex].samp = 1;           // one sample used
            for (mp_sint32 j = 0; j < 120; j++)
                instr[insIndex].snum[j] = i;    // build sample table

            smp[i].samplen = (mp_sint32) LittleEndian::GET_DWORD(ins + 48);    // sample size
            smp[i].loopstart = (mp_sint32) LittleEndian::GET_DWORD(ins + 52); // loop start
            smp[i].looplen = ((mp_sint32) LittleEndian::GET_DWORD(ins + 56)) - ((mp_sint32) LittleEndian::GET_DWORD(ins + 52));   // loop length

            smp[i].vol = module->vol64to255(ins[61]);   // volume

            smp[i].flags = 1;                   // set volume flag

            if ((mp_sint32) LittleEndian::GET_DWORD(ins + 56) > 0 && (ins[47] & 0x80))
            {
                smp[i].type = 1;                // looped sample
            }

            mp_uint32 c4speed = ((mp_uword) LittleEndian::GET_WORD(ins + 62));
            mp_sint32 finetune = ins[60] & 0xf;
            mp_uint32 ftC4Speed = module->sfinetunes[finetune];

            module->convertc4spd(c4speed * ftC4Speed / 8363,
                                 &smp[i].finetune, &smp[i].relnote);
        }

    }

    maxInstruments++;

    header->insnum = maxInstruments;

    MEMSEEK((mp_sint32) LittleEndian::GET_DWORD(buffer + 90));

    //////////////////////
    // read patterns	//
    //////////////////////
    mp_ubyte* pattern = new mp_ubyte[256 * 32 * 5];

    if (pattern == NULL)
    {
        delete[] buffer;
        return -7;
    }

    for (i = 0; i < header->patnum; i++)
    {
        for (mp_sint32 j = 0; j < 32 * 256; j++)
        {
            pattern[j * 5] = 0x00;
            pattern[j * 5 + 1] = 0x00;
            pattern[j * 5 + 2] = 0xFF;
            pattern[j * 5 + 3] = 0x00;
            pattern[j * 5 + 4] = 0x00;
        }

        mp_uword size;
        mp_ubyte numRows, numChannels;

        MEMREAD(&size, 2, 1);

        size = LittleEndian::GET_WORD((mp_ubyte*)&size);

        MEMREAD(&numRows, 1, 1);
        MEMREAD(&numChannels, 1, 1);

        mp_ubyte* packed = new mp_ubyte[size - 4 + 5];
        if (packed == NULL)
        {
            delete[] buffer;
            delete[] pattern;
            return -7;
        }
        memset(packed, 0, size - 4 + 5);

        MEMREAD(packed, 1, size - 4);

        mp_uint32 index = 0;
        mp_uint32 row = 0;

        mp_uint32 maxChannels = 0;

        while (index < size)
        {

            mp_ubyte pi = packed[index++];

            if (pi == 0)
            {
                row++;
            }

            mp_uint32 chn = pi & 31;

            if (chn > maxChannels)
                maxChannels = chn;

            mp_ubyte* slot = pattern + (row * 32 * 5) + chn * 5;

            if (pi & 128)
            {
                slot[0] = packed[index++];
                slot[1] = packed[index++];
            }
            if (pi & 64)
            {
                slot[2] = packed[index++];
            }
            if (pi & 32)
            {
                slot[3] = packed[index++];
                slot[4] = packed[index++];
            }

        }

        maxChannels++;

        if (maxChannels > numChannels)
            maxChannels = numChannels;

        if (maxChannels > header->channum)
            header->channum = maxChannels;

        delete[] packed;

        phead[i].rows = numRows;
        phead[i].effnum = 3;
        phead[i].channum = maxChannels;

        phead[i].patternData = new mp_ubyte[phead[i].rows * maxChannels * 8];

        if (phead[i].patternData == NULL)
        {
            delete[] buffer;
            delete[] pattern;
            return -7;
        }

        memset(phead[i].patternData, 0, maxChannels * 8 * phead[i].rows);

        mp_ubyte* dstSlot = phead[i].patternData;
        for (row = 0; row < phead[i].rows; row++)
            for (mp_uint32 c = 0; c < maxChannels; c++)
            {
                const mp_ubyte* srcSlot = pattern + row * 32 * 5 + c * 5;

                mp_ubyte note = srcSlot[0];

                if (note && (note != 254))
                    note += 24;
                else if (note == 254)
                    note = 122;

                dstSlot[0] = note;

                dstSlot[1] = srcSlot[1];

                if (srcSlot[2] <= 64)
                {

                    dstSlot[2] = 0xC;
                    dstSlot[3] = XModule::vol64to255(srcSlot[2]);

                }

                mp_ubyte eff = srcSlot[3];
                mp_ubyte op = srcSlot[4];

                switch (eff)
                {
                    // fine volume slide up
                    case 01:
                        eff = 0x3A;
                        break;

                    // volume slide up = MDL volslide up
                    case 02:
                        eff = 0x45;
                        op <<= 2;
                        break;

                    // fine volume slide down
                    case 03:
                        eff = 0x3B;
                        break;

                    // volume slide down = MDL volslide down
                    case 04:
                        eff = 0x46;
                        op <<= 2;
                        break;

                    // fine portamento up
                    case 10:
                        eff = 0x31;
                        break;

                    // portamento up
                    case 11:
                        eff = 0x01;
                        break;

                    // fine portamento down
                    case 12:
                        eff = 0x32;
                        break;

                    // portamento down
                    case 13:
                        eff = 0x02;
                        break;

                    // tone porta
                    case 14:
                        eff = 0x03;
                        break;

                    // glissando control
                    case 15:
                        eff = 0x33;
                        break;

                    // tone porta + volslide up
                    case 16:
                        dstSlot[6] = 0x45;
                        dstSlot[7] = op << 2;

                        eff = 0x03;
                        op = 0;
                        break;

                    // tone porta + volslide down
                    case 17:
                        dstSlot[6] = 0x46;
                        dstSlot[7] = op << 2;

                        eff = 0x03;
                        op = 0;
                        break;

                    // vibrato
                    case 20:
                        eff = 0x04;
                        break;

                    // set vibrato waveform
                    case 21:
                        eff = 0x34;
                        break;

                    // vibrato + volslide up
                    case 22:
                        dstSlot[6] = 0x45;
                        dstSlot[7] = op << 2;

                        eff = 0x04;
                        op = 0;
                        break;

                    // vibrato + volslide down
                    case 23:
                        dstSlot[6] = 0x46;
                        dstSlot[7] = op << 2;

                        eff = 0x04;
                        op = 0;
                        break;

                    // tremolo
                    case 30:
                        eff = 0x07;
                        break;

                    // set tremolo waveform
                    case 31:
                        eff = 0x37;
                        break;

                    // set sample offset
                    case 40:
                        eff = 0x09;
                        break;

                    // retrig note
                    case 41:
                        eff = 0x1B;
                        break;

                    // note-cut
                    case 42:
                        eff = 0x3C;
                        break;

                    // note-delay
                    case 43:
                        eff = 0x3D;
                        break;

                    // position jump
                    case 50:
                        eff = 0x0B;
                        break;

                    // pattern break
                    case 51:
                        eff = 0x0D;
                        break;

                    // jump loop
                    case 52:
                        eff = 0x36;
                        break;

                    // pattern delay
                    case 53:
                        eff = 0x3E;
                        break;

                    // set tickspeed
                    case 60:
                        eff = 0x1C;
                        break;

                    // set BPM
                    case 61:
                        eff = 0x16;
                        break;

                    // arpeggio
                    case 70:
                        eff = 0x20;
                        break;

                    // set finetune
                    case 71:
                        eff = 0x35;
                        break;

                    // set panning
                    case 72:
                        eff = 0x08;
                        op *= 17;
                        break;

                    default:
                        #ifdef VERBOSE
                        if (eff != 0)
                        {
                            printf("%i, %x, %x\n", i, eff, op);
                        }
                        #endif
                        //eff = op = 0;
                        break;
                }

                dstSlot[4] = eff;
                dstSlot[5] = op;

                dstSlot += 8;
            }


    }

    /*for (i = 0; i < header->ordnum; i++)
        printf("%i ",header->ord[i]);

       header->ord[0] = 0;
       header->ord[1] = 0;
       header->ord[2] = 6;*/

    delete[] pattern;

    for (i = 0; i < header->smpnum; i++)
    {

        MEMSEEK(samplePtrs[i]);

        smp[i].sample = (mp_sbyte*)module->allocSampleMem(smp[i].samplen);

        if (smp[i].sample == NULL)
        {
            delete[] buffer;
            return -7;
        }

        MEMREAD(smp[i].sample, 1, smp[i].samplen);

        mp_sbyte b1 = 0;
        for (mp_uint32 j = 0; j < smp[i].samplen; j++)
            smp[i].sample[j] = b1 += smp[i].sample[j];

    }

    delete[] buffer;

    module->setDefaultPanning();

    module->postProcessSamples();

    strcpy(header->tracker, "Epic Megagames MASI <old>");

    return 0;
}